/*================ client.c ======================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>     /* for sigaction() */
#include <time.h>     /* for sigaction() */
#include <sys/time.h>     /* for sigaction() */
/*#include <bits/sigaction.h>*/
#include <unistd.h>

#include "util.h"
#include "messages.h"
#include "dbs.h"
#include "client.h"
#include "comunication.h"

/*	Estados	*/
#define WP 0
#define WRR 1
/*	Eventos	*/
#define Packet 0
#define RR 1
#define Timer 2

typedef void (*ActionType)();

int strcasecmp(const char *s1, const char *s2);
char *strdup(const char *s1);
/*int sigfillset(sigset_t *set);*/
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);

char* USERNAME = "defuser";
char* PASSWORD = "defpass";
char *send_pdu;
char *rcv_pdu;
char *message;
int TIMEOUT;
int PUT_MAX;
int SYNCH_MAX;

dbs SON = NULL;

void catch_alarm(int ignored) { /* Handler for SIGALRM */
	TIMEOUT = 1;
}

int processPdu(char *pdu) {
	int i, op;
	op = pdu[1];
	for (i = 0; i < 128 && pdu[i + 2] != '\0'; i++) {
		message[i] = pdu[i + 2];
	}
	return op;
}

void read_servers() {
	char *servers = readFile(SERVERS_FILE);
	char *server, *name, *url, *port_s;
	char* server_aux_list[SERVER_LIST_SIZE];
	int port, i = 0;
	server = strtok(servers, "\n");
	while (server != NULL) {
		server_aux_list[i++] = server;
		server = strtok(NULL, "\n");
	}
	server_aux_list[i] = NULL;
	i = 0;
	while (server_aux_list[i] != NULL && SERVER_LIST_SIZE > i) {
		name = strtok(server_aux_list[i], " \n\t");
		url = strtok(NULL, " \n\t");
		port_s = strtok(NULL, " \n\t");

		if (name == NULL || url == NULL || port_s == NULL)
			break;
		port = atoi(port_s);
		SERVER_LIST[i] = create_dbs(name, url, port);
		++i;
	}
}

/* If there is a server with <server_name>, returns it position in SERVER_LIST */
int check_server_list(char* server_name) {
	int i;
	for (i = 0; SERVER_LIST[i] != NULL; i++) {
		if (strcmp(SERVER_LIST[i]->name, server_name) == 0)
			return i;
	}
	return -1;
}

int run() {

	char input[101], *command, *aux;
	int i;

	while (1) {
		int timeout_aux = 0;
		struct sigaction my_action; /* For setting signal handler */
		my_action.sa_handler = catch_alarm;
		if (sigfillset(&my_action.sa_mask) < 0) /* block everything in handler */
			exit(0);
		my_action.sa_flags = 0;

		if (sigaction(SIGALRM, &my_action, 0) < 0)
			exit(0);

		memset(send_pdu, '\0', 128);
		memset(rcv_pdu, '\0', 128);
		memset(message, '\0', 128);
		/* Command request */
		printf("%s\n", INSERT_COMMAND);
		fgets(input, 100, stdin);

		command = strtok(input, " \n");

		/* command decoding */
		int ok = -1;
		/* PUT */
		if (!strcasecmp(command, PUT)) {
			if (trace)
				printf("[TRACE] Put command\n");
			int sock;
			if ((sock = bks_init_udp())) {
				printf("%s\n", ERROR_PUT_INSUCESS);
				continue;
			}
			char *locale, *number_string, *date;
			locale = strtok(NULL, " ");
			number_string = strtok(NULL, " ");
			date = strtok(NULL, " \n");
			if ((!locale) || (!number_string) || (!date)) {
				printf("%s\n", ERROR_PUT_FORMAT);
				continue;
			}
			send_pdu[0] = strlen(locale) + strlen(number_string) + strlen(date)
					+ strlen(USERNAME) + strlen(PASSWORD) + 7;
			send_pdu[1] = 1;
			send_pdu = strcat(send_pdu, locale);
			send_pdu = strcat(send_pdu, " ");
			send_pdu = strcat(send_pdu, number_string);
			send_pdu = strcat(send_pdu, " ");
			send_pdu = strcat(send_pdu, date);
			send_pdu = strcat(send_pdu, " ");
			send_pdu = strcat(send_pdu, USERNAME);
			send_pdu = strcat(send_pdu, " ");
			send_pdu = strcat(send_pdu, PASSWORD);
			TIMEOUT = 0;
			alarm(T_PUT_MAX);
			ok = bks_send_client_udp(send_pdu, SON->url, SON->port);
			if (trace) {
				printf("[TRACE] Send the PDU: %s\n", send_pdu);
				printf("[TRACE] To Server: %s with port: %d\n", SON->url,
						SON->port);
			}
			if (ok < 0) {
				printf("%s\n", ERROR_PUT_INSUCESS);
				bks_disconnect(sock);
				continue;
			}
			ok = -1;
			while (1) {
				if (TIMEOUT) {
					printf("%s\n", ERROR_PUT_INSUCESS);
					timeout_aux = 1;
					if (trace)
						printf("[TRACE] Expired TputMax\n");
					break;
				}
				ok = bks_rcv_udp(rcv_pdu);
				if (trace)
					printf("[TRACE] Receive the PDU: %s\n", rcv_pdu);
				if (ok > -1) {
					break;
				}
			}
			if (timeout_aux)
				continue;
			alarm(0);
			if (rcv_pdu[1] == 2)
				printf("%s\n", PUT_SUCESS);
			if (rcv_pdu[1] == 3) {
				aux = &(rcv_pdu[2]);
				if (trace)
					printf(
							"[TRACE] Receive the message that %s is no more the SON but %s is\n",
							SON->name, aux);
				dbs new_son = find_dbs(SERVER_LIST, SERVER_LIST_SIZE, aux);
				if (new_son) {
					SON = new_son;
					/*rever o que fazer se receber um novo SON*/
				}
			}
			bks_disconnect(sock);
			if (trace)
				printf("[TRACE] Put done!\n");
			continue;
		}

		/* GET ****************************************************************/
		if (!strcasecmp(command, GET)) {
			if (trace)
				printf("[TRACE] Get command\n");
			char *aux, *locale, *date;
			char* parametros = strtok(NULL, "\n\0");

			int sock;
			sock = bks_create_tcp();

			bks_connect(sock, SON->url, SON->port);

			if (!parametros) {
				if (trace)
					printf("[TRACE] Get all cases\n");
				send_pdu[0] = 3 + strlen(USERNAME) + strlen(PASSWORD);
				send_pdu[1] = 5;
				send_pdu = strcat(send_pdu, USERNAME);
				send_pdu = strcat(send_pdu, " ");
				send_pdu = strcat(send_pdu, PASSWORD);
			} else {
				if (*parametros == 'c') {
					aux = strtok(parametros, " ");
					locale = &aux[2];
					aux = strtok(NULL, " ");

					if (aux != NULL && *aux == 'd') {
						date = &aux[2];
						if (trace)
							printf("[TRACE] Get all cases of %s in %s\n",
									locale, date);
						send_pdu[0] = 5 + strlen(locale) + strlen(date)
								+ strlen(USERNAME) + strlen(PASSWORD);
						send_pdu[1] = 7;
						send_pdu = strcat(send_pdu, USERNAME);
						send_pdu = strcat(send_pdu, " ");
						send_pdu = strcat(send_pdu, PASSWORD);
						send_pdu = strcat(send_pdu, " ");
						send_pdu = strcat(send_pdu, locale);
						send_pdu = strcat(send_pdu, " ");
						send_pdu = strcat(send_pdu, date);

					} else {
						if (trace)
							printf("[TRACE] Get all cases of %s\n", locale);
						send_pdu[0] = 4 + strlen(locale) + strlen(USERNAME)
								+ strlen(PASSWORD);
						send_pdu[1] = 8;
						send_pdu = strcat(send_pdu, USERNAME);
						send_pdu = strcat(send_pdu, " ");
						send_pdu = strcat(send_pdu, PASSWORD);
						send_pdu = strcat(send_pdu, " ");
						send_pdu = strcat(send_pdu, locale);
					}
				} else {
					if (*parametros == 'd') {
						aux = strtok(parametros, " ");
						date = &aux[2];
						aux = strtok(NULL, " ");
						if (aux != NULL && *aux == 'c') {
							locale = &aux[2];
							if (trace)
								printf("[TRACE] Get all cases of %s in %s\n",
										locale, date);
							send_pdu[0] = 5 + strlen(locale) + strlen(date)
									+ strlen(USERNAME) + strlen(PASSWORD);
							send_pdu[1] = 7;
							send_pdu = strcat(send_pdu, USERNAME);
							send_pdu = strcat(send_pdu, " ");
							send_pdu = strcat(send_pdu, PASSWORD);
							send_pdu = strcat(send_pdu, " ");
							send_pdu = strcat(send_pdu, locale);
							send_pdu = strcat(send_pdu, " ");
							send_pdu = strcat(send_pdu, date);
						} else {
							if (trace)
								printf("[TRACE] Get all cases in %s\n", date);
							send_pdu[0] = 4 + strlen(date) + strlen(USERNAME)
									+ strlen(PASSWORD);
							send_pdu[1] = 9;
							send_pdu = strcat(send_pdu, USERNAME);
							send_pdu = strcat(send_pdu, " ");
							send_pdu = strcat(send_pdu, PASSWORD);
							send_pdu = strcat(send_pdu, " ");
							send_pdu = strcat(send_pdu, date);
						}
					}
				}
			}
			/************************************TIMER A METER**************************************************/
			bks_send_tcp(sock, send_pdu);
			if (trace)
				printf("[TRACE] Send the PDU: %s\n", send_pdu);
			/*    adicionat o trace   if (trace)
			 printf("[TRACE] Received PDU: %s\n", send_pdu); */
			bks_rcv_tcp(sock, rcv_pdu);

			if (rcv_pdu[1] == 6)
				printf("OK\n");

			bks_disconnect(sock);
			if (trace)
				printf("[TRACE] Get done\n");
			continue;
		}

		/* SYNCH */

		/*+++++++++++++++++++++++++++++++++++++++METER ISSO A USAR O SON sff+++++++++++++++++++++++++++++++++++++++++++++*/

		if (!strcasecmp(command, SYNCH)) {
			if (trace)
				printf("[TRACE] Synch command\n");
			int sock;
			sock = bks_init_udp();
			char* server_aux[128];
			/*char* active_server[128] = "S0";*/
			int reds = 0;
			int label_tmp = 0;
			int state_tmp = 0;
			int label = 0;
			char name[128];
			char* name_tmp;
			char* son_tmp;
			int server_position;

			send_pdu[0] = 2;
			send_pdu[1] = 11;

			for (i = 0; SERVER_LIST[i] != NULL; i++) {
				strcat(send_pdu, (SERVER_LIST[i])->name);
				bks_send_client_udp(send_pdu, (SERVER_LIST[i])->url,
						(SERVER_LIST[i])->port);
				if (trace) {
					printf("[TRACE] Send the PDU: %s\n", send_pdu);
					printf("[TRACE] To the Server: %s with the port: %d\n",
							(SERVER_LIST[i])->url, (SERVER_LIST[i])->port);
				}
			}
			TIMEOUT = 0;
			alarm(SYNCH_MAX);
			for(i = 0; SERVER_LIST[i] != NULL; i++){
				if(TIMEOUT){
					printf("%s\n", ERROR_SYNCH);
					if (trace)
						printf("[TRACE] Expired TsynchMax\n");
					timeout_aux = 1;
					break;
				}
				memset(rcv_pdu, '\0', 128);
				bks_rcv_udp(rcv_pdu);
				processPdu(rcv_pdu);
				server_aux[i] = strdup(message);
			}
			if (timeout_aux)
				continue;
			alarm(0);
			server_aux[i] = NULL;
			for (i = 0; server_aux[i] != NULL; i++) {
				label_tmp = atoi(strtok(server_aux[i], " "));
				state_tmp = atoi(strtok(NULL, " "));
				son_tmp = strtok(NULL, " ");
				name_tmp = strtok(NULL, "\0");
				if (state_tmp == 2 || state_tmp == 1) {
					server_position = check_server_list(name_tmp);
					if (server_position != -1) {
						SERVER_LIST[server_position]->state = 2;
						SON = SERVER_LIST[server_position];
						if (trace)
							printf("[TRACE] New SON is: %s\n", SON->name);
						reds = -1;
						break;
					}
					/* else server nao na lista */
				}
				if (label_tmp > label) {
					label = label_tmp;
					strcpy(name, name_tmp);
				}
				if (state_tmp == 0)
					reds++;
				free(server_aux[i]);
			}
			if (reds == i) {
				memset(send_pdu, 0, 128);
				send_pdu[0] = 2;
				send_pdu[1] = 12;
				if (label == 0) {
					bks_send_client_udp(send_pdu, (SERVER_LIST[0])->url,
							(SERVER_LIST[0])->port);
					SON = SERVER_LIST[0];
					if (trace)
						printf("[TRACE] New SON is: %s\n", SON->name);
					server_position = 0;
				} else {
					server_position = check_server_list(name_tmp);
					if (server_position != -1) {
						bks_send_client_udp(send_pdu,
								(SERVER_LIST[server_position])->url,
								(SERVER_LIST[server_position])->port);
						SON = SERVER_LIST[server_position];
						if (trace)
							printf("[TRACE] New SON is: %s\n", SON->name);
					}
				}
				memset(send_pdu, 0, 128);
				send_pdu[0] = 3 + strlen((SERVER_LIST[server_position])->name);
				send_pdu[1] = 13;
				strcat(send_pdu, (SERVER_LIST[server_position])->name);
				for (i = 1; SERVER_LIST[i] != NULL; i++) {
					if (i != server_position) {
						bks_send_client_udp(send_pdu, (SERVER_LIST[i])->url,
								(SERVER_LIST[i])->port);
						if (trace) {
							printf("[TRACE] Send PDU: %s\n", send_pdu);
							printf("[TRACE] Tell: %s that new SON is: %s\n",
									(SERVER_LIST[i])->name, SON->name);
						}
					}
				}
			}
			bks_disconnect(sock);
			if (trace)
				printf("[TRACE] Synch done!\n");
			continue;
		}
		/* EXIT */
		if (!strcasecmp(command, QUIT)) {
			break;
		}

		printf("%s\n", ERROR_INVALID_COMMAND);
		i++;
	}
	bks_close();
	return (0);
}

int main(int argc, char *argv[]) {
	send_pdu = (char *) malloc(128 * sizeof(char));
	rcv_pdu = (char *) malloc(128 * sizeof(char));
	message = (char *) malloc(128 * sizeof(char));

	if (argc == 2) {
		if (strcmp(argv[1], "/v") == 0)
			trace = 1;
		else
			USERNAME = argv[1];
	}

	if (argc == 3) {
		if (strcmp(argv[1], "/v") == 0) {
			trace = 1;
			USERNAME = argv[2];
		} else {
			USERNAME = argv[1];
			PASSWORD = argv[2];
		}
	}

	if (argc == 4) {
		trace = 1;
		USERNAME = argv[2];
		PASSWORD = argv[3];
	}

	if (trace) {
		printf("Trace ON\n");
		printf("[TRACE] Username: %s\n", USERNAME);
		printf("[TRACE] Password: %s\n", PASSWORD);
	}

	read_servers();
	SON = SERVER_LIST[0];
	if (SON == NULL) {
		printf("%s\n", ERROR_SERVRS_FILE);
		exit(0);
	}
	if (trace) {
		printf("[TRACE] Server activo: %s\n", SON->name);
	}
	return run();
}

